#!/usr/bin/env php
<?php

use \libs\init;
use \libs\Model;

class kfm
{
    private $db;
    private $filter = '*.git,*.exvim,*.db';

    public function __construct()
    {
        chdir('/data/project/file-manager-kyo');
        define('DS', DIRECTORY_SEPARATOR);
        define('DEBUG', true);
        define('ROOT', __DIR__.DS);

        require_once ROOT.'libs'.DS.'init.class.php';

        init::run();

        $this->db = M('kfm.db');
    }

    // 通用删除函数
    private function _del($args, $table, $where = null, $errstr = null)
    {
        // 判断命令参数是否正常，删除必须得有一个参数
        if (count($args) < 1) {
            // 参数不对的错误提示可自定义
            $errstr == null && $errstr = '缺少参数: '.$table.' del name|ID!';
            E($errstr);
        }

        // 判断删除条件是否自定义, null为不自定义，默认为id和name的判断
        if ($where === null) {
            $where = 'id="'.$args[0].'" or name="'.$args[0].'"';
        } else if (is_array($where)) {
            // where参数为自定义条件，如果是数组则处理组合
            $con = "";
            foreach ($where as $name) {
                $con .= $name.'="'.$args[0].'" or ';
            }
            $where = substr($con, 0, -4);
        }

        $sql = 'select id from '.$table.' where '.$where;
        if ($this->db->query($sql, Model::COL) !== NULL) {
            $this->db->del($where, $table);
            return ($this->db->query($sql, Model::COL) !== NULL) ? 1 : 0;
        }
        return 1;
    }

    // 通用添加函数
    private function _add($data, $table)
    {
        isset($data['ctime']) or $data['ctime'] = time();
        isset($data['mtime']) or $data['mtime'] = $data['ctime'];

        $this->db->insert($data, null, $table);

        return 0;
    }

    // 通用列表显示函数
    private function _show_list($sql, $formater)
    {
        if (stripos($sql, 'select ') === FALSE) {
            $this->db->table = $sql;
            $data = $this->db->select();
        } else {
            $data = $this->db->query($sql);
        }
        $show = "";
        foreach ($data as $row) {
            $show .= call_user_func_array($formater, [$row]);
        }
        if ($show === "")
            return 1;

        pcntl_exec('/bin/bash', ['-c', 'echo "'.$show.'" | less -XRF']);

        return 0;
    }

    // 项目创建操作函数
    public function create($argv)
    {
        if (count($argv) < 1)
            E("错误: 缺少 name 参数!");

        $arr = explode('\n', $argv[0]);
        $data['name'] = $arr[0];
        $data['remark'] = isset($arr[1]) ? $arr[1] : "";

        return $this->_add($data, 'kfm_project');
    }

    // 项目删除操作函数
    public function drop($argv)
    {
        return $this->_del($argv, 'kfm_project');
    }

    private function _project_list()
    {
        return $this->_show_list('kfm_project', function($row) {
            $show = sprintf("\033[36;1m%-3d ", $row['id']);
            $show .= "\033[34;1m".$row['name']." ";
            $n = sqlCol('select count(*) from kfm_file where pid='.$row['id']);
            $show .= sprintf("\033[32;1m%4d\033[0m ", $n);
            $show .= "\033[37m".$row['remark']."\033[0m ";
            return $show;
        });
    }

    private function _show_file_format($file)
    {
        $show = sprintf("\033[36;1m%-3d\033[0m ", $file['id']);
        $show .= "\033[33m".$file['md5']."\033[0m  ";
        $show .= sprintf("\033[33;1m%7s\033[0m ", psize($file['size']));
        $show .= sprintf("\033[35;1m%s\033[0m\n", $file['mime']);
        $show .= 'CTime:   '.date('Y-m-d H:i:s', $file['ctime']). PHP_EOL;
        $pname = sqlCol('select name from kfm_project where id='.$file['pid']);
        $mount = get_mount_path($file['did']);
        $show .= 'Project: '.$file['pid'].'/'.$pname.PHP_EOL;
        $show .= 'Path:    ';
        $show .= $mount.$file['dir'].DS.$file['name']."\n\n";
        // $show .= "1m".$file['name']."\033[0m  ";
        // $show .= "\033[37m".$file['remark']."\033[0m\n\n";
        return $show;
    }

    private function _file_list($opt, $argv)
    {
        // $limit = $opt['limit'] ?? 10;
        if (isset($opt['sql']))
            $sql = $opt['sql'];
        else if (isset($opt['where'])) {
            $sql = 'select * from kfm_file where '.$opt['where'];
            if (isset($opt['limit']))
                $sql .= ' limit '.$limit;
        } else {
            $sql = 'select * from kfm_file where 1';
            if (count($argv) > 0) {
                $sql .= ' and name in (';
                foreach ($argv as $name) {
                    $sql .= '"'.$name.'",';
                }
                $sql = rtrim($sql, ',').')';
            }
            if (isset($opt['project']))
                $sql .= ' and pid='.get_pid($opt['project']);
            if (isset($opt['tag']))
                $sql .= ' and tag like "%'.$opt['tag'].'%"';
            if (isset($opt['limit']))
                $sql .= ' limit '.$limit;
        }
        // echo $sql, PHP_EOL;
        // exit(0);

        return $this->_show_list($sql, [$this, '_show_file_format']);
    }

    public function _list($argv)
    {
        if (count($argv) == 0)
            return $this->_project_list();

        $opt = [
            'where' => ':--where',
            'sql' => ':--sql',
            'project' => ':p|-p|--project',
            'tag' => ':t|-t|--tag',
            'limit' => ':l|-l|--limit'
        ];
        kyo_getopt($opt, $argv);
        return $this->_file_list($opt, $argv);
    }

    private function _add_file_do($path, $data, & $log)
    {
        if (is_dir($path))
            $reVal = $this->_add_dir($path, $data);
        else
            $reVal = $this->_add_file($path, $data);

        if (is_string($reVal)) {
            $log['error'] .= $reVal;
            $log['errCount'] += 1;
        } else if (is_array($reVal)) {
            $log['error'] .= $reVal['error'];
            $log['errCount'] += $reVal['errCount'];
            $log['success'] .= $reVal['success'];
            $log['addCount'] += $reVal['addCount'];
        } else {
            $log['success'] .= $path.PHP_EOL;
            $log['addCount'] += 1;
        }
    }

    private function _add_dir($dirname, $data)
    {
        $log = [
                'error' => '',
                'success' => '',
                'errCount' => 0,
                'addCount' => 0
               ];

        $dp = opendir($dirname);

        while (($filename = readdir($dp)) != FALSE) {
            // if ($filename === "." || $filename === "..")
            if ($filename[0] === ".")
                continue;
            $this->_add_file_do($dirname.DS.$filename, $data, $log);
        }
        closedir($dp);

        return $log;
    }

    private function _add_file($file, $data)
    {
        // 如果文件不是目录则判断此文件是否存在, 存在则报错
        $data['md5'] = null;
        if (!$this->exists([$file, '-q'], $data['md5'])) {
            return $file.": 数据库中已存在!\n";
        }

        // 获取文件信息进行添加操作
        $msg = get_file_msg($file, $data);
        // print_r($ret);
        if (is_string($msg))
            return $file.' '.$msg;

        return $this->_add($data, 'kfm_file');
    }

    public function add($argv)
    {
        if (count($argv) < 2) {
            $str = "错误: 必须指定项目及添加的文件!\n";
            $str .= "Usage: kfm add project filename|dirname!";
            E($str);
        }

        // 获取及判断项目ID正确性
        $id = array_shift($argv);
        $where = ['id' => $id, 'name' => $id];
        $data['pid'] = get_data_id('kfm_project', $where);
        $data['pid'] === NULL and E('错误：指定的项目不存在!');

        // 整理添加参数(-t -m -b -e)
        $opt = [
                    'tag'    => ':t|-t|--tag',
                    'remark' => ':m|-m|--message',
                    'is_bkp' => ':b|-b|--backup',
                    'is_en'  => ':e|-e|--encrypt',
                    'filter' => ':f|-f|--filter',
               ];
        $data = kyo_getopt($opt, $argv) + $data;

        // var_dump($data);
        // var_dump($argv);

        $log = [
                'error' => '',
                'success' => '',
                'errCount' => 0,
                'addCount' => 0
               ];
        $add_success_log = '/tmp/kfm_add_success'.mt_rand(10, 99).'.log';
        $add_error_log = '/tmp/kfm_add_error'.mt_rand(10, 99).'.log';

        foreach ($argv as $file) {
            // 判断文件是否存在进行报错
            if (!file_exists($file)) {
                $error .= $file.": 文件或目录不存在!\n";
                $error_num++;
                continue;
            }

            // 如果是目录，则调用添加目录的方法,否则添加添加文件的方法
            $this->_add_file_do($file, $data, $log);
        }

        printf("\033[31;1mError:\033[0m\n\033[37m%s\033[0m", $log['error']);
        printf("------------------------------------\n");

        file_put_contents($add_success_log, $log['success']);
        file_put_contents($add_error_log, $log['error']);

        printf("%3d 个文件添加成功, %3d 个文件添加失败!\n",
                    $log['addCount'], $log['errCount']);
        printf("添加详细情况请查看 %s 和 %s 日志文件内容!\n",
                    $add_success_log, $add_error_log);

        return 0;
    }

    public function rm($argv)
    {
        if (count($argv) == 0)
            E('错误: 必须指定文件或ID!');

        $opt = [
            'where' => ':--where',
            'project' => ':p|-p|--project',
            'tag' => ':t|-t|--tag',
        ];
        kyo_getopt($opt, $argv);

        if (isset($opt['where'])) {
            $where = $opt['where'];
        } else {
            if (count($argv) > 0) {
                $ids =  'id in (0,';
                $names = 'name in ("",';
                foreach ($argv as $name) {
                    if (is_int($name))
                        $ids .= $name.',';
                    else
                        $names .= '"'.$name.'",';
                }
                $ids = rtrim($ids, ',').')';
                $names = rtrim($names, ',').')';
                $where = ' ('.$ids.' or '.$names.')';
            }

            if (isset($opt['project']))
                $where .= ' and pid='.get_pid($opt['project']);
            if (isset($opt['tag']))
                $where .= ' and tag like "%'.$opt['tag'].'%"';
        }

        $delCount = sqlCol('select count(*) from kfm_file where '.$where);
        printf("确定要删除 %d 条记录吗(y/n)? ", $delCount);
        $ch = rtrim(fgets(STDIN));
        if (!($ch == 'y' || $ch == 'Y'))
            return 0;

        return $this->db->del($where, 'kfm_file');
    }

    public function exists($argv, & $md5 = FALSE)
    {
        if (count($argv) < 1)
            E("错误: 缺少参数!");

        // 获取文件的md5检验和
        $md5sum = md5_file($argv[0]);

        // 查询数据库对应md5检验和的数据记录
        $sql = 'select * from kfm_file where md5="'.$md5sum.'"';
        $msg = $this->db->query($sql, Model::ROW);

        // 判断参数md5是否传入，如果传入则将md5检验和赋给引用参数
        $md5 !== FALSE and $md5 = $md5sum;

        // 如果查询记录为空, 返回1, 此返回值主要是给shell返回的
        if (count($msg) == 0 || $msg === NULL || $msg === FALSE)
            return 1;

        // 如果查询记录不为空，则根据参数-q是否存在输出查询记录
        if (isset($argv[1]) && ($argv[1] == '-q' || $argv[1] == '--quiet'))
            return 0;

        echo $this->_show_file_format($msg);

        return 0;
    }

    public function push($argv)
    {
        //第二阶段考虑....
    }

    public function disk_list($argv = null)
    {
        return $this->_show_list('kfm_disk', function($row) {
            $show = sprintf("\033[36;1m%-3d\033[0m ", $row['id']);
            $show .= sprintf("\033[33m%-37s ", $row['uuid']);
            $show .= sprintf("\033[32;1m%-7s ", psize($row['dsize'], 1));
            $show .= sprintf("\033[33;1m%-11s ", $row['format']);
            $show .= "\033[34;1m".$row['name']."\n";
            $show .= "\033[37mRemark: ".$row['remark']."\033[0m\n";
            $show .= "\n";
            return $show;
        });
    }

    public function disk_add($argv)
    {
        if (count($argv) < 2)
            E('缺少参数: kfm disk add dev-file name\nremark!');

        $disk = get_dev_msg($argv[0]);
        $name = explode('\n', $argv[1]);
        $disk['remark'] = count($name) == 2 ? $name[1]: "";
        $disk['name'] = $name[0];

        return $this->_add($disk, 'kfm_disk');
    }

    public function disk_del($argv)
    {
        return $this->_del($argv, 'kfm_disk');
    }

    public function disk(& $argv)
    {
        $argc = count($argv);
        if ($argc == 0)
            return $this->disk_list();

        $method = 'disk_'.array_shift($argv);
        if (method_exists($this, $method))
            return $this->$method($argv);

        E("disk 命令参数不匹配!");
    }

    static public function run($argc, $argv)
    {
        if ($argc < 2) {
            printf("kfm 没有指定命令参数!\n");
            return 1;
        }

        $obj = new kfm();

        $cmd = get_command($argv);
        switch ($cmd) {
            case 'create':
            case 'c':
                return $obj->create($argv);
            case 'drop':
            case 'd':
                return $obj->drop($argv);
            case 'list':
            case 'l':
                return $obj->_list($argv);
            case 'add':
                return $obj->add($argv);
            case 'rm':
                return $obj->rm($argv);
            case 'exists':
            case 'e':
                return $obj->exists($argv);
            case 'push':
                return $obj->push($argv);
            case 'disk':
                return $obj->disk($argv);
            case 'clear':
                system('rm /tmp/kfm_*.log -f');
                return;
            default:
                E("kfm 不能识别的命令!");
        }
    }
}

exit((int)(kfm::run($argc, $argv)));
